﻿Imports EIBWatcherNet.Core.EIB.Bus.Addressing
Imports System.IO.Ports

Namespace EIB.Bus
    Public NotInheritable Class EIBBusAccessModule_FT12Serial
        Implements IEIBBusAccessModule

        Private com As SerialPort = Nothing

        Private _toggleBit As Boolean = 0

        Public Event LogBusData(reason As EIBBusAccessLogReason, message As String, data As Byte()) Implements IEIBBusAccessModule.LogBusData
        Public Event ReceiveBusData(address As EIBAddress, value() As Byte) Implements IEIBBusAccessModule.ReceiveBusData


        Public Function OpenConnection(ParamArray params() As Object) As Boolean Implements IEIBBusAccessModule.OpenConnection
            If params Is Nothing OrElse params.Count < 1 OrElse Not TypeOf params(0) Is String Then Throw New Exceptions.EIBException("")
            Dim portName As String = params(0)

            If com IsNot Nothing AndAlso com.IsOpen Then com.Close()

            Try
                com = New SerialPort(portName, 19200, Parity.Even, 8, StopBits.One)
                com.Handshake = Handshake.None
                com.ReadBufferSize = 2 ^ 16
                com.WriteBufferSize = 2 ^ 16
                com.ReadTimeout = 2000
                com.Open()

                InitializeBCU()
            Catch ex As Exception
                RaiseEvent LogBusData(EIBBusAccessLogReason.Error, String.Format("Error while open port: {0}", ex.Message), Nothing)

                Return False
            End Try

            Return True
        End Function

        Public Sub CloseConnection() Implements IEIBBusAccessModule.CloseConnection
            If com IsNot Nothing AndAlso com.IsOpen Then com.Close()
        End Sub

        Public ReadOnly Property IsConnected As Boolean Implements IEIBBusAccessModule.IsConnected
            Get
                Return If(com Is Nothing, False, com.IsOpen)
            End Get
        End Property


        Public Sub ReadBusData(address As EIBGroupAddress) Implements IEIBBusAccessModule.ReadBusData
            Dim msg As New EIB.Bus.EIBBusData_FrameLData(EIB.BusData.EIBBusData_FramePriority.Normal,
                                             address,
                                             7,
                                             0,
                                             EIB.BusData.EIBBusData_FrameACPI_L_Data_Type.Read, New Byte() {0}
                                            )

            SendVariableFrame(msg.BusData)
        End Sub

        Public Sub RespondBusData(address As EIBGroupAddress, value As Byte()) Implements IEIBBusAccessModule.RespondBusData
            Dim msg As New EIB.Bus.EIBBusData_FrameLData(EIB.BusData.EIBBusData_FramePriority.Normal,
                                             address,
                                             7,
                                             0,
                                             EIB.BusData.EIBBusData_FrameACPI_L_Data_Type.Response, value
                                            )

            SendVariableFrame(msg.BusData)
        End Sub

        Public Sub WriteBusData(address As EIBGroupAddress, value As Byte()) Implements IEIBBusAccessModule.WriteBusData
            Dim msg As New EIB.Bus.EIBBusData_FrameLData(EIB.BusData.EIBBusData_FramePriority.Normal,
                                                         address,
                                                         7,
                                                         0,
                                                         EIB.BusData.EIBBusData_FrameACPI_L_Data_Type.Write, value
                                                        )

            SendVariableFrame(msg.BusData)
        End Sub


        Private Function InitializeBCU() As Boolean
            'Send reset
            'If Not ResetBCU() Then Return False
            Dim data As Byte() = EIB.Bus.FT12.FT12_FrameBase.FT12_Reset
            com.Write(data, 0, data.Length)
            Log(EIBBusAccessLogReason.OutgoingMessage, data)

            Dim a As Byte = com.ReadByte
            Log(EIBBusAccessLogReason.IncomingMessage, a)
            If Not a = EIB.Bus.FT12.FT12_FrameBase.FT12_ACK Then Return False

            'data = New Byte() {&H68, &H8, &H8, &H68, &H73, &HA9, &H96, &H18, &H34, &H56, &H78, &HA, &HD6, &H16}
            data = New Byte() {&H68, &H8, &H8, &H68, &H73, &HA9, &H1E, &H12, &H34, &H56, &H78, &H9A, &HE8, &H16}
            com.Write(data, 0, data.Length)
            Log(EIBBusAccessLogReason.OutgoingMessage, data)

            a = com.ReadByte
            Log(EIBBusAccessLogReason.IncomingMessage, a)
            If Not a = EIB.Bus.FT12.FT12_FrameBase.FT12_ACK Then Return False

            'data = New Byte() {&H68, &H6, &H6, &H68, &H53, &HA6, &H1, &H1, &H16, &H0, &H11, &H16}
            data = New Byte() {&H68, &H8, &H8, &H68, &H53, &HA9, &H0, &H18, &H34, &H56, &H78, &HA, &H20, &H16}
            com.Write(data, 0, data.Length)
            Log(EIBBusAccessLogReason.OutgoingMessage, data)

            a = com.ReadByte
            Log(EIBBusAccessLogReason.IncomingMessage, a)
            If Not a = EIB.Bus.FT12.FT12_FrameBase.FT12_ACK Then Return False

            _toggleBit = 0


            Dim buffer As New List(Of Byte)
            AddHandler com.DataReceived, Sub(_sender As Object, _e As SerialDataReceivedEventArgs)
                                             While com.BytesToRead > 0
                                                 Dim b As Byte = com.ReadByte

                                                 buffer.Add(b)

                                                 If buffer.Count = 1 AndAlso b = EIB.Bus.FT12.FT12_FrameBase.FT12_ACK Then
                                                     'ACK received
                                                     buffer.Clear()

                                                     Log(EIBBusAccessLogReason.IncomingMessage, EIB.Bus.FT12.FT12_FrameBase.FT12_ACK)
                                                 End If
                                                 If b = EIB.Bus.FT12.FT12_FrameBase.FT12_END Then
                                                     If buffer(0) = EIB.Bus.FT12.FT12_FrameBase.FT12_STARTFIXED Then
                                                         'fixed length frame
                                                         If buffer.Count < 4 Then
                                                             'wait for more data
                                                         ElseIf buffer.Count = 4 Then
                                                             'frame ready
                                                             com.Write(EIB.Bus.FT12.FT12_FrameBase.FT12_ACK)

                                                             'decode
                                                             Dim busdata As Byte() = buffer.ToArray

                                                             buffer.Clear()
                                                             Log(EIBBusAccessLogReason.IncomingMessage, busdata)
                                                             Log(EIBBusAccessLogReason.OutgoingMessage, EIB.Bus.FT12.FT12_FrameBase.FT12_ACK)
                                                         Else
                                                             'error, daten zu lang (>4 Byte)
                                                             buffer.Clear()

                                                             Log(EIBBusAccessLogReason.Error, "currupt fixed length frame received: missing End field")
                                                         End If
                                                     ElseIf buffer(0) = EIB.Bus.FT12.FT12_FrameBase.FT12_STARTVARIABLE Then
                                                         'variable length frame
                                                         If buffer(1) <> buffer(2) Then
                                                             'error
                                                             Log(EIBBusAccessLogReason.Error, "corrupt data received: variable length frame Length fields do not match")
                                                         Else
                                                             If buffer.Count < buffer(1) + 5 Then
                                                                 'wait for more data
                                                             ElseIf buffer.Count = buffer(1) + 5 OrElse
                                                                    buffer.Count = buffer(1) + 6 Then
                                                                 'frame ready
                                                                 'com.Write(FT12_Handler.FT12_ACK)

                                                                 'decode
                                                                 Dim busdata As Byte() = buffer.ToArray

                                                                 buffer.Clear()
                                                                 Log(EIBBusAccessLogReason.IncomingMessage, busdata)
                                                                 Log(EIBBusAccessLogReason.OutgoingMessage, EIB.Bus.FT12.FT12_FrameBase.FT12_ACK)

                                                                 Dim ft12 As New FT12.FT12_FrameVariable(busdata)
                                                                 Dim EIBtype = EIB.Bus.EIBBusData_FrameBase.GetMessageType(DirectCast(ft12, EIB.Bus.FT12.FT12_FrameVariable).Data)

                                                                 If EIBtype = EIB.BusData.EIBBusData_EMICode.L_Data_con OrElse
                                                                    EIBtype = EIB.BusData.EIBBusData_EMICode.L_Data_ind OrElse
                                                                    EIBtype = EIB.BusData.EIBBusData_EMICode.L_Data_req Then

                                                                     Dim msg As New EIB.Bus.EIBBusData_FrameLData(DirectCast(ft12, EIB.Bus.FT12.FT12_FrameVariable).Data)

                                                                     RaiseEvent ReceiveBusData(msg.DestinationAddress, msg.Data)
                                                                 ElseIf EIBtype = EIB.BusData.EIBBusData_EMICode.L_Busmon_ind Then
                                                                     Dim msg As New EIB.Bus.EIBBusData_FrameLBusMonData(DirectCast(ft12, EIB.Bus.FT12.FT12_FrameVariable).Data)

                                                                     RaiseEvent ReceiveBusData(msg.DestinationAddress, msg.Data)
                                                                 End If
                                                             Else
                                                                 'error, Daten zu lang (>(length+6) Byte)
                                                                 buffer.Clear()

                                                                 Log(EIBBusAccessLogReason.Error, "corrupt variable length frame received: missing End field")
                                                             End If
                                                         End If
                                                     Else
                                                         'error, unknown header
                                                         buffer.Clear()

                                                         Log(EIBBusAccessLogReason.Error, "corrupt data received: unknown Start field")
                                                     End If
                                                 End If
                                             End While
                                         End Sub
            AddHandler com.ErrorReceived, Sub(_sender As Object, _e As SerialErrorReceivedEventArgs)

                                          End Sub

            Return True
        End Function

        Private Sub SendFixedFrame(primaryMessage As Boolean, messageCode As Byte)
            Dim ft12 As EIB.Bus.FT12.FT12_FrameFixed

            If primaryMessage Then
                _toggleBit = Not _toggleBit

                ft12 = New EIB.Bus.FT12.FT12_FrameFixed(EIB.Bus.FT12.FT12_FrameDirection.PCtoBAU,
                                                        _toggleBit,
                                                        True,
                                                        messageCode
                                                       )
            Else
                ft12 = New EIB.Bus.FT12.FT12_FrameFixed(EIB.Bus.FT12.FT12_FrameDirection.PCtoBAU,
                                                        False,
                                                        messageCode
                                                       )
            End If

            WritePort(ft12.BusData)
        End Sub

        Private Sub SendVariableFrame(data As Byte())
            _toggleBit = Not _toggleBit

            Dim ft12 As New EIB.Bus.FT12.FT12_FrameVariable(EIB.Bus.FT12.FT12_FrameDirection.PCtoBAU,
                                                            _toggleBit,
                                                            True,
                                                            EIB.Bus.FT12.FT12_FramePrimaryMessageCode.SEND_UDAT,
                                                            data
                                                           )

            WritePort(ft12.BusData)
        End Sub

        Private Function ResetBCU() As Boolean Implements IEIBBusAccessModule.ResetBCU
            'Send reset
            Dim data As Byte() = EIB.Bus.FT12.FT12_FrameBase.FT12_Reset
            com.Write(data, 0, data.Length)
            Log(EIBBusAccessLogReason.OutgoingMessage, data)

            Dim a As Byte = com.ReadByte
            Log(EIBBusAccessLogReason.IncomingMessage, a)
            If Not a = EIB.Bus.FT12.FT12_FrameBase.FT12_ACK Then Return False

            Return True
        End Function

        Private Sub GetLinkStatus() Implements IEIBBusAccessModule.GetLinkStatus
            Dim data As Byte() = New Byte() {EIB.Bus.FT12.FT12_FrameBase.FT12_STARTFIXED, &H49, &H49, EIB.Bus.FT12.FT12_FrameBase.FT12_END}
            WritePort(data)
        End Sub

        Private Sub WritePort(data As Byte())
            If com IsNot Nothing AndAlso com.IsOpen Then
                com.Write(data, 0, data.Length)
            End If

            Log(EIBBusAccessLogReason.OutgoingMessage, data)
        End Sub


        Private Sub Log(reason As EIBBusAccessLogReason, data As Byte())
            RaiseEvent LogBusData(reason, EIB.Helper.ConvertByteArrayToHexString(data), data)
        End Sub

        Private Sub Log(reason As EIBBusAccessLogReason, data As Byte)
            RaiseEvent LogBusData(reason, EIB.Helper.ConvertByteToHexString(data), New Byte() {data})
        End Sub

        Private Sub Log(reason As EIBBusAccessLogReason, message As String)
            RaiseEvent LogBusData(reason, message, Nothing)
        End Sub
    End Class
End Namespace